排列
Time Limit: 10 Sec Memory Limit: 128 MB
Description
给一个数字串s和正整数d, 统计s有多少种不同的排列能被d整除(可以有前导0)。
例如123434有90种排列能被2整除,其中末位为2的有30种,末位为4的有60种。
输入第一行是一个整数T,表示测试数据的个数,以下每行一组s和d,中间用空格隔开。
Output
每个数据仅一行,表示能被d整除的排列的个数。
7
000 1
001 1
1234567890 1
123434 2
1234 7
12345 17
12345678 29
Sample Output
1
3
3628800
90
3
6
1398
HINT
s的长度不超过10, 1<=d<=1000, 1<=T<=15
Solution
我们运用状压DP,令 f[j][opt] 表示当前余数为 j,状态为opt的方案。
状态记录的是:各个数字被用了几次。
那么我们就可以状压了。先DFS出每个状态,记sum[k]表示后缀积,那么显然 从 opt 转移到 第k个数字多用一次的状态 就是 opt + sum[k + 1]。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| #include<bits/stdc++.h> using namespace std; typedef long long s64;
const int ONE = 20005;
int T, n, m; int num; int vis[ONE], Num[20], sum[20]; int f[1005][20005]; int Sta[ONE][10]; char ch[ONE];
int get() { int res=1,Q=1;char c; while( (c=getchar())<48 || c>57 ) if(c=='-')Q=-1; res=c-48; while( (c=getchar())>=48 && c<=57 ) res=res*10+c-48; return res*Q; }
void Dfs(int T) { if(T > 10) { num++; for(int i = 0; i <= 9; i++) Sta[num][i] = vis[i]; return; }
for(int i = 0; i <= Num[T]; i++) vis[T] = i, Dfs(T + 1); }
void Deal() { memset(f, 0, sizeof(f)); memset(Num, 0, sizeof(Num)); memset(sum, 0, sizeof(sum)); num = 0; scanf("%s", ch + 1); m = get(); n = strlen(ch + 1);
for(int i = 1; i <= n; i++) Num[ch[i] - '0']++; sum[10] = 1; for(int i = 9; i >= 0; i--) sum[i] = sum[i + 1] * (Num[i] + 1);
Dfs(0);
f[0][1] = 1; for(int opt = 1; opt <= num; opt++) for(int j = 0; j < m; j++) if(f[j][opt]) for(int k = 0; k <= 9; k++) { if(Sta[opt][k] >= Num[k]) continue; int to = opt + sum[k + 1]; f[(j * 10 + k) % m][to] += f[j][opt]; }
printf("%d\n", f[0][num]); }
int main() { T = get(); while(T--) Deal(); }
|